﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using FeedsLibrary.Feeds.RSS;

namespace FeedsLibrary.Feeds.Abstract
{
    public abstract class AbstractChannel : IChannel
    {
        #region public props
        //the # of days worth of items to keep
        protected int Days { get; set; }

        //only keep the stories that match the tags
        public bool TagsOnly { get; set; }

        //the news stories for this channel
        //Items.  earliest at start and latest at end.
        public List<IItem> Items { get; set; }

        public DateTime CurrentDay { get; set; }

        #endregion

        private string _lastGuid;
        private string _xml = "";

        protected XmlDocument Document { get; set; }

        #region abstract methods

        public abstract IItem GetNewItem( string domain, IXPathNavigable nodeItem, DateTime lastBuildDate );

        protected abstract void LoadBody( bool update );

        #endregion

        #region protected methods


        protected AbstractChannel()
        {
            Items=new List<IItem>();
        }

        protected static XmlNode FindElement(IXPathNavigable node, string elementName)
        {
            XmlNode result = null;
            XmlNode nodeRss = node as XmlNode;

            try
            {
                // Loop for the <rss> tag
                for ( int i = 0; i < nodeRss.ChildNodes.Count; i++ )
                {
                    // If it is the rss tag
                    if ( nodeRss.ChildNodes[ i ].Name == elementName )
                    {
                        // <rss> tag found
                        result = nodeRss.ChildNodes[ i ];
                        break;
                    }
                }
            }
            catch ( Exception )
            {
                Console.WriteLine( "Error finding - " + elementName );
                throw;
            }
            return result;
        }

        protected void LoadItems( XmlNode node, string itemName, bool update )
        {
            if (node == null)
            {
                throw new Exception("node is null");
            }
            try
            {
                string tempGuid = "";
                TimeSpan gap = new TimeSpan( Days, 0, 0, 0 );
                DateTime cutOff = CurrentDay.Subtract(gap);

                // Loop for the <title>, <link>, <description> and all the other tags
                for ( int i = 0; i < node.ChildNodes.Count; i++ )
                {
                    //System.Windows.Forms.Application.DoEvents()

                    // If it is the item tag, then it has children tags which we will add as items to the ListView
                    if ( node.ChildNodes[ i ].Name == itemName )
                    {
                        IItem ii = GetNewItem( Title, node.ChildNodes[ i ], LastBuildDate );

                        ii.IconUrl = IconUrl;

                        //update
                        if ( update )
                        {
                            //remember first new guid
                            if ( String.IsNullOrEmpty(tempGuid) )
                            {
                                tempGuid = ii.Guid;
                            }

                            //then add item
                            if ( ii.Guid != _lastGuid )
                            {
                                if ( ii.PubDate > cutOff )
                                {
                                    Items.Add( ii );
                                }
                            }
                            else
                            {
                                break;
                            }
                        }
                            //add
                        else
                        {
                            if ( _lastGuid == null )
                            {
                                _lastGuid = ii.Guid;
                            }

                            if ( ii.PubDate > cutOff )
                            {
                                Items.Add( ii );
                            }
                        }
                    }
                }
                if ( update && ! string.IsNullOrEmpty(tempGuid ))
                {
                    _lastGuid = tempGuid;
                }

                Items.Sort();
                Items.Reverse();
            }
            catch ( Exception )
            {
                Title = "Error reading feed " + Url;
                Items.Clear();
            }
            finally
            {
                Document = null;
            }
        }

        protected void LoadDocumentFromUrl(Uri sourceUrl, Uri iconUrl)
        {
            try
            {
                Url = sourceUrl;
                Document = new XmlDocument();
                IconUrl = iconUrl;

                if ( sourceUrl.AbsoluteUri.StartsWith( @"http://" ))
                {
                    HttpWebRequest request = ( HttpWebRequest )WebRequest.Create( sourceUrl );
                    request.Timeout = 2000;
                    request.UserAgent = "Bond, James Bond";
                    HttpWebResponse response = ( HttpWebResponse )request.GetResponse();

                    // Load the XML content into a XmlDocument
                    Document.Load( new XmlTextReader( response.GetResponseStream() ) );
                }
                else
                {
                    using (XmlTextReader t=new XmlTextReader(Uri.UnescapeDataString( sourceUrl.LocalPath) ))
                    {
                        Document.Load( t );
                    }
                }
            }
            catch ( Exception )
            {
                Console.WriteLine( "Error reading - " + sourceUrl );
            }
        }

        protected void LoadHeader( XmlNode node )
        {
            try
            {
                if ( node[ "title" ] != null )
                {
                    Title = node[ "title" ].InnerText;
                }

                if ( node[ "language" ] != null )
                {
                    Language = node[ "language" ].InnerText;
                }

                if ( node[ "link" ] != null )
                {
                    Link = node[ "link" ].InnerText;
                }

                if ( node[ "description" ] != null )
                {
                    Description = node[ "description" ].InnerText;
                }

                if ( node[ "lastBuildDate" ] != null )
                {
                    LastBuildDate = ( node[ "lastBuildDate" ].InnerText.Length > 0
                                          ? GetDate( node[ "lastBuildDate" ].InnerText )
                                          : DateTime.Now );
                }
            }
            catch ( Exception )
            {
                Title = "Error reading feed " + Url;
                Items.Clear();
            }
        }

        #endregion

        #region private methods

        protected static DateTime GetDate( string date )
        {
            int i = 0;

            string strDiff = date.Substring(date.LastIndexOf(" ", StringComparison.InvariantCulture) + 1);

            if ( date.EndsWith( "GMT" ) )
            {
                //string s = "";
                date = date.Substring(0, date.LastIndexOf(" ", StringComparison.InvariantCulture));
            }
            else if ( date.EndsWith( "PST" ) || date.EndsWith( "PDT" ) )
            {
                //string s = "";
                date = date.Substring(0, date.LastIndexOf(" ", StringComparison.InvariantCulture));
                i = 8;
            }
            else if ( date.EndsWith( "BST" ) )
            {
                //string s = "";
                date = date.Substring(0, date.LastIndexOf(" ", StringComparison.InvariantCulture));
                i = 1;
            }
            else if ( date.EndsWith( "EST" ) || date.EndsWith( "EDT" ) )
            {
                //string s = "";
                date = date.Substring(0, date.LastIndexOf(" ", StringComparison.InvariantCulture));
                i = 5;
            }
            else
            {
                if ( Int32.TryParse( strDiff, out i ) )
                {
                    strDiff = strDiff.Substring( 0, strDiff.Length - 2 );
                    i = Int32.Parse( strDiff );
                    if ( i < 0 )
                    {
                        i = i + ( i * -2 );
                    }
                    else
                    {
                        i = i - ( i * 2 );
                    }
                }
            }

            DateTime result;
            try
            {
                result = DateTime.Parse( date );
                result = result.AddHours( i );
            }
            catch ( Exception )
            {
                result = DateTime.Now;
            }


            return result;
        }

        #endregion

        #region public methods

        public MemoryStream Stream()
        {
            StringBuilder sb = GetXML();
            using (MemoryStream m = new MemoryStream())
            {
                Byte[] b = Encoding.ASCII.GetBytes( sb.ToString() );
                m.Write( b, 0, b.Length - 1 );
                m.Position = 0;

                return m;
            }
        }

        public void Save( string fileName )
        {
            UTF8Encoding utf8 = new UTF8Encoding();
            byte[] _message;

            StringBuilder sb = GetXML();
            if ( _xml.Length == 0 )
            {
                _xml = sb.ToString();
            }

            //creating the xmltextwriter 
            using (MemoryStream ms = new MemoryStream(utf8.GetBytes(_xml)))
            {
                XmlTextWriter xmlw = new XmlTextWriter( ms, new UTF8Encoding() );
                xmlw.Formatting = Formatting.Indented;

                //turning the memorystream into a byte[] array so I can pass to to my socket later 
                _message = ms.ToArray();
                //This is pivitol, using ms.GetBuffer() results in the extra padding that exists in the memorystream. 
            }

            //now how I wrote it to file, I test was if it opened in IE (which uses the MSXML parser, then the java server on the other end using sax would be happy) 
            if ( File.Exists( fileName ) )
            {
                File.Delete( fileName );
            }

            using (Stream outputStream = File.OpenWrite(fileName))
            {
                outputStream.Write( _message, 0, _message.Length );
                outputStream.Flush();
            }
        }


        private StringBuilder GetXML()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append( @"<rss version=""2.0"" " );
            sb.Append( @"xmlns:prizereader=""http://www.thirdprize.org/"" >" + "\r\n" );
            sb.Append( "<channel>\r\n" );
            sb.Append( "<title>" + SecurityElement.Escape( Title ) + "</title>\r\n" );
            sb.Append( "<description>" + SecurityElement.Escape( Description ) + "</description>\r\n" );
            sb.Append( "<link>" + SecurityElement.Escape( Link ) + "</link>\r\n" );
            if ( TagList != null )
            {
                sb.Append("<prizereader:tagDistribution>" +
                           SecurityElement.Escape(TagList.SmallestValue() + "," + TagList.LargestValue()) +
                           "</prizereader:tagDistribution>\r\n");
                sb.Append("<prizereader:lastBuildDate>" +
                           SecurityElement.Escape(LastBuildDate.ToShortDateString()) +
                           "</prizereader:lastBuildDate>\r\n");
                sb.Append("<prizereader:lastBuildTime>" +
                           SecurityElement.Escape(LastBuildDate.ToShortTimeString()) +
                           "</prizereader:lastBuildTime>\r\n");
            }

            List<string> prevStories = new List<string>();
            foreach ( IItem t1 in Items )
            {
                AbstractItem it = ( AbstractItem )t1;
                if ( !prevStories.Contains( it.Title ) && ( (TagsOnly && it.Tags!= null) || !TagsOnly))
                {
                    sb.Append( it.Save() );
                    prevStories.Add( it.Title );
                }
            }
            sb.Append( "</channel>\r\n" );
            sb.Append( "</rss>\r\n" );

            return sb;
        }

        public IChannel Search( string queryTerm )
        {
            AbstractChannel c = new RssChannel();
            foreach ( IItem ii in Items )
            {
                if ( ii.Contains( queryTerm.ToLower(CultureInfo.InvariantCulture) ) )
                {
                    c.Items.Add( ii );
                }
            }
            c.Items.Sort();
            Items.Reverse();
            return c;
        }

        public object this[ int index ]
        {
            get
            {
                return Items[ index ];
            }
        }

        public int ItemCount()
        {
            return Items.Count;
        }

        public void Merge(IChannel channel)
        {

            for (int i = 0; i < channel.ItemCount(); i++)
            {
                AbstractItem it = (AbstractItem)channel[i];
                Items.Add( it );
            }
            Items.Sort();
            Items.Reverse();
        }

        public void ParseTags( TagList tags )
        {
            if (tags == null)
            {
                throw new Exception("theTags  is null");
            }

            foreach ( IItem ii in Items )
            {
                List<string> t = tags.ParseTags(ii.Title + " " + ii.Description);
                if ( t.Count > 0 )
                {
                    ii.Tags = t;
                }
            }
        }

        public void Update()
        {
            LoadBody( true );
        }

        #endregion

        #region properties

        public string Language { get; set; }

        public DateTime LastBuildDate { get; set; }

        public string Title { get; set; }

        public string Link { get; set; }

        public Uri Url { get; set; }

        public Uri IconUrl { get; set; }

        public string Description { get; set; }

        public TagList TagList { get; set; }

        public string FileName { get; set; }

        #endregion
    }
}